﻿// -------------------------------------------------------------------------
//    @FileName         :    NFBagPageOp.h
//    @Author           :    gaoyi
//    @Date             :    2024/12/20
//    @Email            :    445267987@qq.com
//    @Module           :    NFBagPageOp
//
// -------------------------------------------------------------------------

// --- 文件总结 ---------------------------------------------------------------
// `NFBagPageOp` 负责对单个背包页签进行增删改查与遍历操作，提供：
// - 添加：优先叠加到可堆叠格子，再填充空格子（支持 WillAddItemData 与 NFGridItem）
// - 删除：按物品ID/按格子/按筛选参数/按UUID 删除（尽量/必须模式）
// - 移动：跨页签移动或同类合并移动
// - 查询：获取数量、遍历物品/描述、首个空格、错误码等
// - 提交：将操作格子写回实际容器，记录变更统计并生成通知
//
// 注意：
// - 本类不直接操作真实容器，变更缓存在操作格子中，只有在 Commit 时写入
// - 绝不拷贝（复制构造/赋值删除），避免半操作状态被复制
// ---------------------------------------------------------------------------

#pragma once

#include "NFBagPageOpDefine.h"
#include <proto_cs.pb.h>
#include <NFLogicCommon/NFItemDefine.h>

class NFBagPageGrid;
class NFBagPageInterface;
class NFBagPageOpMgrInterface;

/**
 * @brief 单个背包页签的操作器
 * - 负责在操作层对物品进行添加、删除、移动、遍历与统计
 * - 通过 Commit/PreCheckCommit 与实际容器交互，提交时落库并记录变更
 * - 与 `NFBagPageInterface` 协作屏蔽底层实现细节
 */
class NFBagPageOp
{
    friend class NFBagPageOpMgrInterface;
    friend class NFBagPageOpMgr;
    friend class NFStorageBagOpMgr;
public:
    NFBagPageOp();
    virtual ~NFBagPageOp();
public: ///添加物品
    /**
     * @brief 添加物品到指定空格子
     * @param stWillAddItemData 待添加物品数据
     * @param iNum 要添加的数量（不能超过堆叠数）
     * @param iGridIndex 目标格子索引
     * @return 0成功，非0失败
     */
    int AddItemToEmptyGridByGridIndex(const WillAddItemData& stWillAddItemData, int iNum, int iGridIndex);
    
    /**
     * @brief 添加物品到指定空格子（物品对象版本）
     * @param pItem 要添加的物品对象
     * @param iGridIndex 目标格子索引
     * @return 0成功，非0失败
     */
    int AddItemToEmptyGridByGridIndex(const NFGridItem* pItem, int iGridIndex);

    /**
     * @brief 添加物品到指定可叠加格子
     * @param stWillAddItemData 待添加物品数据
     * @param iNum 要添加的数量（不能超过堆叠数）
     * @param iGridIndex 目标格子索引（格子不能为空且必须可叠加此物品）
     * @return 0成功，非0失败
     */
    int AddItemToPileGridByGridIndex(const WillAddItemData& stWillAddItemData, int iNum, int iGridIndex);

    /**
     * @brief 添加多个物品到可叠加格子（优先叠加到已有同类格子）
     * @param stWillAddItemData 待添加物品数据
     * @param iNum 要添加的数量（可超过堆叠数，可添加到多个格子）
     * @param iAddNum 输出实际添加数量（<= iNum）
     * @return 0成功，非0失败
     */
    int AddItemToPileGrid(const WillAddItemData& stWillAddItemData, int iNum, int& iAddNum);
    
    /**
     * @brief 添加多个物品到可叠加格子（物品对象版本）
     * @param pItem 要添加的物品对象
     * @param iAddNum 输出实际添加数量
     * @return 0成功，非0失败
     */
    int AddItemToPileGrid(const NFGridItem* pItem, int& iAddNum);

    /**
     * @brief 添加多个物品到空格子（将剩余数量落空格，遵循每格上限）
     * @param stWillAddItemData 待添加物品数据
     * @param iNum 要添加的数量（可超过堆叠数，可添加到多个空格子）
     * @param iAddNum 输出实际添加数量（<= iNum）
     * @return 0成功，非0失败
     */
    int AddItemToEmptyGrid(const WillAddItemData& stWillAddItemData, int iNum, int& iAddNum);
    
    /**
     * @brief 仅添加物品到空格子（不尝试叠加）
     * @param pItem 要添加的物品对象
     * @param iWantAddNum 期望添加数量
     * @param iAddNum 输出实际添加数量
     * @return 0成功，非0失败
     */
    int AddItemOnlyToEmptyGrid(const NFGridItem* pItem, int iWantAddNum, int& iAddNum);
public:
    /**
     * @brief 添加物品（直接用现有物品对象）
     * @param pItem 要添加的物品（个数不可超过堆叠数）
     * @param iAddNum 输出实际添加数量
     * @return 0成功，非0失败
     * @note 不可叠加物品直接找空槽放，可叠加物品先尝试叠加
     */
    int AddItem(const NFGridItem* pItem, int& iAddNum);
    
    /**
     * @brief 添加指定数量的物品（物品对象版本）
     * @param pItem 要添加的物品对象
     * @param iWantAddNum 期望添加数量
     * @param iAddNum 输出实际添加数量
     * @return 0成功，非0失败
     */
    int AddItem(const NFGridItem* pItem, int iWantAddNum, int& iAddNum);

    /**
     * @brief 添加多个物品（先叠加，再放空槽）
     * @param stWillAddItemData 待添加物品数据
     * @param iNum 要添加的数量（可超过堆叠数，可添加到多个格子）
     * @param iAddNum 输出实际添加数量（<= iNum）
     * @return 0成功，非0失败
     */
    int AddItem(const WillAddItemData& stWillAddItemData, int iNum, int& iAddNum);

public: ///删除物品
    /**
     * @brief 删除指定物品（可能从多个格子扣除）
     * @param ullOwnerRoldID 删除谁的物品（=0 不校验物品的owner）
     * @param iItemId 要删除的物品ID
     * @param iWantDelNum 要删除的数量（可超过堆叠数）
     * @param pRealDelNum 输出实际删除数量（=NULL 必须全部删除，!=NULL 尽可能删除）
     * @return 0成功，非0失败
     * @note 不够时，会从 GetLinkDelCWareContainerOp 页签补扣
     */
    int DelItem(uint64_t ullOwnerRoldID, int iItemId, int iWantDelNum, int* pRealDelNum);
    
    /**
     * @brief 删除指定物品（ullOwnerRoleID = 操作者）
     * @param iItemId 要删除的物品ID
     * @param iWantDelNum 要删除的数量
     * @param pRealDelNum 输出实际删除数量（=NULL 必须全部删除，!=NULL 尽可能删除）
     * @return 0成功，非0失败
     */
    int DelItem(int iItemId, int iWantDelNum, int* pRealDelNum);
    
    /**
     * @brief 减少指定格子的物品数量
     * @param iGridIndex 要减少物品的格子索引
     * @param iWantDecNum 要减少的数量（>0）
     * @param pRealDecNum 输出实际减少数量（=NULL 必须减少够，!=NULL 尽可能减少）
     * @return 0成功，非0失败
     */
    int DelItemByGridWithNum(int iGridIndex, int iWantDecNum, int* pRealDecNum = nullptr);
    
    /**
     * @brief 减少指定格子的物品数量（格子对象版本）
     * @param stGrid 格子对象
     * @param iWantDecNum 要减少的数量
     * @param pRealDecNum 输出实际减少数量
     * @return 0成功，非0失败
     */
    int DelItemByGridWithNum(NFBagPageGrid& stGrid, int iWantDecNum, int* pRealDecNum = nullptr);

    /**
     * @brief 删除指定格子的物品
     * @param iGridIndex 要删除物品的格子索引
     * @return 0成功，非0失败
     */
    int DelItemByGrid(int iGridIndex);

    /**
     * @brief 删除所有指定物品
     * @param iItemId 要删除的物品ID
     * @return 0成功，非0失败
     */
    int DelAllByItemID(int iItemId);
    
    /**
     * @brief 删除所有符合筛选规则的格子中的物品
     * @param stParam 物品筛选规则
     * @return 0成功，非0失败
     */
    int DelAllMatchGrid(const SelectItemGridParam& stParam);

    /**
     * @brief 删除符合筛选规则的一批物品（尽量/必须）
     * @param stParam 物品筛选规则
     * @param iWantDecNum 要减少的数量（>0）
     * @param pRealDecNum 输出实际减少数量（=NULL 必须减少够，!=NULL 尽可能减少）
     * @return 0成功，非0失败
     * @note 会从 GetLinkDelCWareContainerOp 页签扣
     */
    int DelItem(SelectItemGridParam& stParam, int iWantDecNum, int* pRealDecNum = nullptr);

    /**
     * @brief 根据物品ID和UUID删除物品
     * @param itemId 物品ID
     * @param ullItemUUid 物品UUID
     * @return 0成功，非0失败
     */
    int DelItemByItemIdAndUUid(int itemId, uint64_t ullItemUUid);

public: ///移动物品
    /**
     * @brief 将本容器指定格子的物品移动到目标容器
     * @param iGridIndex 要移动的格子索引
     * @param iMoveNum 移动数量（=-1 全部移动）
     * @param pBagPageOp 目标容器操作器
     * @return 0成功，非0失败
     */
    int MoveGridItemToBagPage(int iGridIndex, int iMoveNum, NFBagPageOp* pBagPageOp);

    /**
     * @brief 将本容器指定格子的物品移动到目标容器（完整版本）
     * @param iGridIndex 要移动的格子索引
     * @param iMoveNum 移动数量（=-1 移动全部，>0 指定个数）
     * @param stToBagPageOp 目标容器操作器
     * @param pRealMoveNum 输出实际移动数量（=NULL 必须全部移动，!=NULL 尽量移动）
     * @param iCheckNum 校验格子中物品数量（=-1 不校验）
     * @return 0成功，非0失败
     */
    int MoveOneGridItemToOtherBagPage(int iGridIndex, int iMoveNum, NFBagPageOp& stToBagPageOp, int* pRealMoveNum, int iCheckNum = -1);

    /**
     * @brief 转移所有同类物品
     * @param pToBagPage 目标容器操作器
     * @param bHadMove 输出是否移动了物品
     * @param pNoSameItem 输出未移动时是否因目标容器没有相同物品（可选）
     * @return 0成功，非0失败
     * @note 本容器中物品ID若存在于目标容器，就向目标容器尽量移动此物品
     */
    int TransferSameItemId(NFBagPageOp* pToBagPage, bool& bHadMove, bool* pNoSameItem = nullptr);

    /**
     * @brief 将指定格子的物品移动到目标容器的相同物品ID格子
     * @param iFromGridIndex 源格子索引
     * @param stToBagPage 目标容器操作器
     * @param iMoveNum 输出实际移动数量
     * @param bNoSameItem 输出是否因目标容器没有相同物品而未移动（true=不存在相同物品）
     * @return 0成功，非0失败
     * @note 本容器格子中的物品ID若存在于目标容器，则尽量移动到目标容器
     */
    int AddOneGridToSameResID(int iFromGridIndex, NFBagPageOp& stToBagPage, int& iMoveNum, bool& bNoSameItem);

    /**
     * @brief 将物品尽量添加到本容器中相同物品ID的格子
     * @param pItem 要添加的物品
     * @param iAddNum 输出实际添加数量
     * @param bNoSameItem 输出是否因不存在相同物品而未添加（true=不存在相同物品）
     * @return 0成功，非0失败
     * @note 本容器中存在相同物品ID时，将物品尽量添加到本容器
     */
    int AddSameItemIDToBagPage(NFGridItem* pItem, int& iAddNum, bool& bNoSameItem);

public:
    /**
     * @brief 获取物品读写指针
     * @param iGridIndex 格子索引
     * @return 物品指针（需要判空）
     * @note 获取过的格子会被标记为已修改，commit时写入真实容器
     */
    NFGridItem* GetMutableItem(int iGridIndex);

    /**
     * @brief 获取物品复制品用于检测（不更改物品）
     * @param iGridIndex 格子索引
     * @param pOutItem 输出物品复制品
     * @return 0成功，非0失败
     * @note 优先从操作格子取，没有时从原始容器取；需要判空
     */
    int GetItemForCheck(int iGridIndex, NFGridItem* pOutItem);
    
    /**
     * @brief 获取物品复制品用于检测（同时获取原始物品）
     * @param iGridIndex 格子索引
     * @param pOutItem 输出操作物品指针
     * @param pOutOriginItem 输出原始物品指针
     * @return 0成功，非0失败
     */
    int GetItemForCheck(int iGridIndex, NFGridItem*& pOutItem, NFGridItem*& pOutOriginItem);

    /**
     * @brief 获取操作过的格子的物品指针（用于检测，不更改物品）
     * @param iGridIndex 格子索引
     * @return 物品指针（需要判空）
     */
    NFGridItem* GetGridItemForCheck(int iGridIndex);

    /**
     * @brief 获取原始物品（在实际容器中的物品，commit前不会变动）
     * @param iGridIndex 格子索引
     * @return 物品指针（需要判空）
     */
    NFGridItem* GetOriginItem(int iGridIndex);

    /**
     * @brief 获取操作物品数据
     * @param iItemID 物品ID（必填）
     * @param pstParam 获取指定条件的物品参数
     * @param vecItemOpData 输出物品操作数据列表（必然全部填充且有效）
     * @return 0成功，非0失败
     */
    int GetOpWareData(int iItemID, GetOpItemParam* pstParam, std::vector<ItemOpData>& vecItemOpData);

public:
    /**
     * @brief 获取物品总个数
     * @param iItemId 物品ID
     * @param iItemNum 输出物品数量
     * @return 0成功，非0失败
     */
    int GetItemNum(int iItemId, int& iItemNum);

    /**
     * @brief 获取物品个数（带上限）
     * @param iItemId 物品ID
     * @param iMaxNum 最大数量（超过返回>=iMaxNum，不够返回实际个数）
     * @param iItemNum 输出物品数量
     * @return 0成功，非0失败
     */
    int GetItemNum(int iItemId, int iMaxNum, int& iItemNum);

    /**
     * @brief 获取页签容量
     * @return 页签容量
     */
    int GetCapacity() const { return m_iCapacity; }

    /**
     * @brief 获取页签类型
     * @return 页签类型
     */
    int GetBagType() const { return m_iBagType; }
public:///物品遍历
    /**
     * @brief 遍历页签中的所有物品
     * @param func 遍历回调函数
     *        - iGridIndex: 物品所在格子（不能使用物品上的格子数据，因为被操作过的物品格子只在最后commit时才赋值）
     *        - pOriginItem: 未被操作过的物品（原始物品）
     *        - pItem: 操作过的物品
     *        - bStop: 输出是否要继续遍历（未设置时=false）
     * @return 0成功，非0失败
     * @note pOriginItem 和 pItem 有且只有一个!=NULL
     */
    int ForEachItem(const std::function<int(int iGridIndex, NFGridItem* pOriginItem, NFGridItem* pItem, bool& bStop)>& func);
    
    /**
     * @brief 遍历页签中的所有物品（const引用版本）
     * @param func 遍历回调函数
     *        - iGridIndex: 物品所在格子
     *        - stItem: 物品引用
     *        - bStop: 输出是否要继续遍历
     * @return 0成功，非0失败
     */
    int ForEachItem(const std::function<int(int iGridIndex, const NFGridItem& stItem, bool& bStop)>& func);

    /**
     * @brief 遍历页签中的所有物品的配置数据
     * @param func 遍历回调函数
     *        - iGridIndex: 物品所在格子
     *        - pItem: 物品配置描述
     *        - bStop: 输出是否要继续遍历（未设置时=false）
     * @return 0成功，非0失败
     */
    int ForEachItemDesc(const std::function<int(int iGridIndex, const E_Item* pItem, bool& bStop)>& func);
public:
    /**
     * @brief 检查格子索引是否合法
     * @param iGridIndex 格子索引（有效范围 [0, GetCapacity())）
     * @return 0合法，非0不合法
     */
    int CheckGridIndex(int iGridIndex) const;

    /**
     * @brief 查找第一个空格子（从头开始遍历）
     * @param iGridIndexOut 输出格子索引（<0时未找到）
     * @return 0成功，非0失败
     */
    int FindFirstEmptyGrid(int& iGridIndexOut);

    /**
     * @brief 检查是否有操作格子
     * @param iGridIndex 格子索引
     * @return true有操作格子，false无
     */
    bool HaveGrid(int iGridIndex) const;

    /**
     * @brief 获取页签已满错误码
     * @param iErrCode 输出错误码（一定!=0）
     * @return 0成功，非0失败
     */
    int GetErrCodeWhenFull(int& iErrCode);

    /**
     * @brief 设置格子物品数量
     * @param iGridIndex 格子索引
     * @param iNum 要设置的数量
     * @param iRealNum 输出实际设置的数量
     * @return 0成功，非0失败
     */
    int SetGridNumber(int iGridIndex, int iNum, int& iRealNum);
    
    /**
     * @brief 设置格子物品数量（格子对象版本）
     * @param stGrid 格子对象
     * @param iNum 要设置的数量
     * @param iRealNum 输出实际设置的数量
     * @return 0成功，非0失败
     */
    int SetGridNumber(NFBagPageGrid& stGrid, int iNum, int& iRealNum);
protected:
    /** @note 禁止复制：复制会导致莫名问题，比如操作一半了复制给另一个，commit时会覆盖 */
    NFBagPageOp(const NFBagPageOp&) = delete;
    NFBagPageOp& operator=(const NFBagPageOp&) = delete;
protected:
    /** @brief 背包页签操作管理器 */
    NFBagPageOpMgrInterface* m_pBagPageOpMgr;
    /** @brief 背包页签接口 */
    NFBagPageInterface* m_pBagPageInterface;
    /** @brief 所有者ID */
    uint64_t m_ownerId;
    /** @brief 页签容量 */
    int m_iCapacity;
    /** @brief 页签类型 */
    int m_iBagType;

    /** @brief 操作格子列表 */
    NFShmVector<NFBagPageGrid*, BAG_PAGE_CAPACITY_MAX> m_astGridList;
    /** @brief 操作格子索引列表 */
    NFShmVector<int, BAG_PAGE_CAPACITY_MAX> m_astGridIndexList;

    /** @brief 物品数量变更记录（iItemId -> iNum） */
    std::unordered_map<int, int> m_astItemNumChange;
private:
    /**
     * @brief 选取符合规则的格子
     * @param stParam 筛选参数
     * @param vecGridIndexs 输出格子索引列表
     * @return 0成功，非0失败
     */
    int SelectItemGrid(const SelectItemGridParam& stParam, std::vector<int>& vecGridIndexs);
private:
    /**
     * @brief 检查物品是否可以添加到本页签
     * @param stWillAddItemData 待添加物品数据
     * @param bCanAdd 输出是否可以添加
     * @return 0成功，非0失败
     */
    int ItemCanAdd(const WillAddItemData& stWillAddItemData, bool& bCanAdd);
    
    /**
     * @brief 检查物品是否可以添加到本页签（物品对象版本）
     * @param pItem 物品对象
     * @param bCanAdd 输出是否可以添加
     * @return 0成功，非0失败
     */
    int ItemCanAdd(const NFGridItem* pItem, bool& bCanAdd);
public:
    /**
     * @brief 获取是否同一个容器
     * @param pOther 另一个页签操作器（必须!=NULL）
     * @param bEqual 输出是否同一个容器（底层持有的实际容器一致就是同一个）
     * @return 0成功，非0失败
     */
    int GetEqual(const NFBagPageOp* pOther, bool& bEqual) const;

    /**
     * @brief 获取操作格子（不存在时会创建并复制实际容器格子）
     * @param iGridIndex 格子索引 [0, GetCapacity())
     * @return 格子指针
     */
    NFBagPageGrid* GetGrid(int iGridIndex);

    /**
     * @brief 获取实际操作的容器
     * @return 背包页签接口指针（一定存在，不用检测）
     * @note 在commit前，里面的数据不会有任何变动，用来获取容器原始数据
     */
    NFBagPageInterface* GetBagPageInterface() { return m_pBagPageInterface; }
    
    /**
     * @brief 获取实际操作的容器（const版本）
     * @return 背包页签接口指针（一定存在，不用检测）
     */
    const NFBagPageInterface* GetBagPageInterface() const { return m_pBagPageInterface; }

    /**
     * @brief 获取所有者ID
     * @return 所有者ID
     */
    uint64_t GetOwnerId() const { return m_ownerId; }
    
    /**
     * @brief 设置所有者ID
     * @param ownerId 所有者ID
     */
    void SetOwnerId(uint64_t ownerId) { m_ownerId = ownerId; }

    /**
     * @brief 获取背包页签操作管理器
     * @return 背包页签操作管理器指针
     */
    NFBagPageOpMgrInterface* GetBagPageOpMgr() { return m_pBagPageOpMgr; }
    
    /**
     * @brief 获取背包页签操作管理器（const版本）
     * @return 背包页签操作管理器指针
     */
    const NFBagPageOpMgrInterface* GetBagPageOpMgr() const { return m_pBagPageOpMgr; }
protected:
    /**
     * @brief 获取已存在的操作格子（不存在时返回nullptr）
     * @param iGridIndex 格子索引 [0, GetCapacity())
     * @return 操作格子指针（可能为nullptr）
     */
    NFBagPageGrid* GetExistOpGrid(int iGridIndex);
    
    /**
     * @brief 根据索引获取操作格子（不存在时返回nullptr）
     * @param iGridIndex 格子索引
     * @return 操作格子指针（可能为nullptr）
     */
    NFBagPageGrid* GetOpGridByIndex(int iGridIndex);
    
    /**
     * @brief 根据索引获取操作格子（const版本）
     * @param iGridIndex 格子索引
     * @return 操作格子指针（可能为nullptr）
     */
    const NFBagPageGrid* GetOpGridByIndex(int iGridIndex) const;

    /**
     * @brief 获取已有的操作格子中的物品（可直接修改物品属性）
     * @param iGridIndex 格子索引
     * @return 物品指针（可能为nullptr）
     */
    NFGridItem* GetExistMutableItem(int iGridIndex);

    /**
     * @brief 提交所有操作到实际容器（用操作过的格子覆盖容器对应格子）
     * @param opWareRec 输出物品变化记录 <物品ID, 数量变化>
     * @return 0成功，非0失败
     */
    int Commit(std::unordered_map<int,int>& opWareRec);

    /**
     * @brief 提交单个格子到实际容器
     * @param pCurGrid 要提交的格子
     * @param bPreCheckFlag 是否为预检查模式
     * @return 0成功，非0失败
     */
    int CommitGrid(NFBagPageGrid* pCurGrid, bool bPreCheckFlag = false);

    /**
     * @brief 初始化要操作的物品容器
     * @param pBagPageMgr 背包页签操作管理器
     * @param pBagPageInterface 背包页签接口
     * @return 0成功，非0失败
     */
    int Init(NFBagPageOpMgrInterface* pBagPageMgr, NFBagPageInterface* pBagPageInterface);

    /**
     * @brief 标记格子为已使用（有操作格子）
     * @param iGridIndex 格子索引
     * @return 0成功，非0失败
     */
    int SetUseGrid(int iGridIndex);

    /**
     * @brief 创建操作格子
     * @param iGridIndex 格子索引 [0, GetCapacity())
     * @param pGrid 输出创建的格子指针
     * @return 0成功，非0失败
     */
    int CreateGrid(int iGridIndex, NFBagPageGrid*& pGrid);
    
    /**
     * @brief 预检查提交（检查所有操作格子是否可以提交）
     * @return 0成功，非0失败
     */
    int PreCheckCommit();

    /**
     * @brief 填充背包更新通知消息（包含所有提交的格子）
     * @param pNtf 输出通知消息
     * @param nOPReason 操作原因
     * @return 0成功，非0失败
     */
    int FillPackageInfoNotify(proto_ff::Proto_CSRoleItemNtf* pNtf, int nOPReason);
    
    /**
     * @brief 填充背包添加物品通知消息
     * @param pNtf 输出通知消息
     * @param nOPReason 操作原因
     * @return 0成功，非0失败
     */
    int FillPackageInfoForAdd(proto_ff::Proto_CSRoleItemNtf* pNtf, int nOPReason);
    
    /**
     * @brief 填充背包删除物品通知消息
     * @param pNtf 输出通知消息
     * @param nOPReason 操作原因
     * @return 0成功，非0失败
     */
    int FillPackageInfoForDel(proto_ff::Proto_CSRoleItemNtf* pNtf, int nOPReason);
};



